package edu.hawaii.duedates.parser;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;


/**
 * Argument is an abstract class that assists in parsing the simplest of options
 * given a String array. An argument passed can be classified as either two different
 * categories. 1) An option
 *             2) An option with parameters.
 * All subclasses that extend from here will only need to define two abstract methods which
 * determine what an option and a parameter is.      
 * 
 * @author Anthony M. Du
 *
 */
public abstract class Argument {
  // List of all options.
  private final List<Option> options = new ArrayList<Option>();
  // All arguments passed.
  private final List<String> arguments = new ArrayList<String>();

  /**
   * Parse parses out the given String array of arguments.
   * @param argv - String array of arguments.
   */
  protected void parse(String[] argv) {
    try {
      Collections.addAll(arguments, argv);
      for (String arg : argv) {
        handleOption(arg);
      }
    }
    catch (ArrayIndexOutOfBoundsException e) {
      System.err.println(e);
    }
  }

  /**
   * Adds an argument to the list of arguments.
   * @param arg - String containing the argument being passed.
   * @return boolean of true/false.
   */
  protected boolean addArgument(String arg) {
    return arguments.add(arg);
  }

  /**
   * Gets the list of all arguments.
   * @return List of all arguments.
   */
  protected List<String> getArguments() {
    return arguments;
  }

  /**
   * Adds an argument to the list of options.
   * @param option - String containing the option's name being passed.
   * @return boolean of true/false.
   */
  protected boolean addOption(String option) {
    if (isOption(option)) {
      return options.add(new Option(option));
    } 
    return false;
  }

  /**
   * Gets the list of all options.
   * @return List of all options.
   */
  public List<Option> getOptions() {
    return options;
  }

  /**
   * Finds the given option in the option list.
   * @param option  option instance.
   * @return Option  option object from option list.
   */
  public Option findOption(Option option) {
    Option opt = null;
    for (Iterator<Option> it = options.iterator(); it.hasNext();) {
      if ((opt = (Option) it.next()).compareTo(option) == 0) {
        break;
      }
      else {
        opt = null;
      }
    }
    return opt;
  }

  /**
   * Finds the option from the option list given the option's name.
   * @param option  option's name.
   * @return Option  option object from option list.
   */
  public Option findOption(String option) {
    Option opt = null;
    for (Iterator<Option> it = options.iterator(); it.hasNext();) {
      if ((opt = (Option) it.next()).compareTo(new Option(option)) == 0) {
        break;
      }
      else {
        opt = null;
      }
    }
    return opt;
  }  

  /**
   * Checks whether or not an option if contained in the option list given the option object.
   * @param option  object containing the option.
   * @return boolean  true/false.
   */
  public boolean isOptionExists(Option option) {
    return options.contains(option);
  }

  /**
   * Checks whether or not an option is contained in the option list given the option's name.
   * @param option  option's name.
   * @return boolean  true/false.
   */
  public boolean isOptionExists(String option) {
    for (Iterator<Option> it = options.iterator(); it.hasNext();) {
      if (((Option) it.next()).compareTo(new Option(option)) == 0) {
        return true;
      }
    }
    return false;
  }
  
  /**
   * IsOption checks whether or not an option passed is in fact an option.
   * @param option - String containing the option being passed.
   * @return boolean of true/false.
   */
  public abstract boolean isOption(String option);

  /**
   * IsParam checks whether or not an argument passed is in fact a parameter.
   * @param param - String containing the option being passed.
   * @return boolean of true/false.
   */
  public abstract boolean isParam(String param);

  /**
   * IsOptParam checks whether or not an argument passed is in fact an option with parameter(s).
   * @param option - String containing the option being passed.
   * @return boolean of true/false.
   * @throws IndexOutOfBoundsException if index access is invalid.
   */
  public boolean isOptParam(String option) throws IndexOutOfBoundsException {
    int optionIndex = getArguments().indexOf(option);
    if (((optionIndex + 1) <= getArguments().size() - 1)
        && (isOption(option)) && (isParam(getArguments().get(optionIndex + 1)))) {
      return true;
    }
    return false;
  }

  /**
   * Deals with classifying options and storing them in the option list.
   * @param option - String containing the option being passed.
   */
  private void handleOption(String option) {
    if (isOption(option)) {
      // Adds option to option parameter list.
      addOption(option);
      if (isOptParam(option)) {
        // Stores the last option added to option list.
        Option optParam = options.get(options.size() - 1);
        // Stores the index of optParam.
        int optionIndex = getArguments().indexOf(option);
        // Store all parameters from the base option until the next (but not including) option.
        for (int i = optionIndex + 1; i < getArguments().size(); i++) {
          if (isParam(getArguments().get(i))) {
            optParam.addParam(getArguments().get(i));
          } 
          else { // Exit loop once next option is found.
            break;
          }
        }
      }
    }
  }
}
